package server;

import java.io.*;
import java.net.*;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.logging.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import client.ServerCommunicator;

import com.sun.net.httpserver.*;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import data.*;
import database.*;

public class Server {

	private static int SERVER_PORT_NUMBER;
	private static String SERVER_HOST_NAME;
	private static final int MAX_WAITING_CONNECTIONS = 10;
	private XStream xmlStream;
	private static Logger logger;

	static {
		try {
			initLog();
		} catch (IOException e) {
			System.out.println("Could not initialize log: " + e.getMessage());
		}
	}

	private static void initLog() throws IOException {

		Level logLevel = Level.FINE;

		logger = Logger.getLogger("record-indexer");
		logger.setLevel(logLevel);
		logger.setUseParentHandlers(false);

		Handler consoleHandler = new ConsoleHandler();
		consoleHandler.setLevel(logLevel);
		consoleHandler.setFormatter(new SimpleFormatter());
		logger.addHandler(consoleHandler);

		FileHandler fileHandler = new FileHandler("log.txt", false);
		fileHandler.setLevel(logLevel);
		fileHandler.setFormatter(new SimpleFormatter());
		logger.addHandler(fileHandler);
	}

	private HttpServer server;

	private Server(int portNum) {
		SERVER_PORT_NUMBER = portNum;
		return;
	}

	private void run() {
		xmlStream = new XStream(new DomDriver());
		logger.info("Initializing Database");

		try {
			Database.initialize();
		} catch (Exception e) {
			e.printStackTrace();
			logger.log(Level.SEVERE, e.getMessage(), e);
			return;
		}

		logger.info("Initializing HTTP Server");

		try {
			server = HttpServer.create(
					new InetSocketAddress(SERVER_PORT_NUMBER),
					MAX_WAITING_CONNECTIONS);
			
		} catch (IOException e) {
			e.printStackTrace();
			logger.log(Level.SEVERE, e.getMessage(), e);
			return;
		}

		server.setExecutor(null); // use the default executor

		server.createContext("/ValidateUser", validateUser);
		server.createContext("/GetProjects", getProjects);
		server.createContext("/GetSampleImage", getSampleImage);
		server.createContext("/DownloadBatch", downloadBatch);
		server.createContext("/SubmitBatch", submitBatch);
		server.createContext("/GetFields", getFields);
		server.createContext("/Search", search);

		logger.info("Starting HTTP Server");

		server.start();
	}

	private String buildRequestString(HttpExchange exchange, String body) {

		StringBuilder sb = new StringBuilder();
		sb.append(exchange.getRequestMethod() + " ");
		sb.append(exchange.getRequestURI().getPath() + " ");
		sb.append(exchange.getProtocol() + "\n");

		Headers headers = exchange.getRequestHeaders();
		for (Entry<String, List<String>> entry : headers.entrySet()) {
			sb.append(entry.getKey() + ":");
			for (String str : entry.getValue()) {
				sb.append(" " + str);
			}
			sb.append("\n");
		}
		sb.append(body);
		return sb.toString();
	}

	private void sendResponse(HttpExchange exchange, Object input, int code) {
		logger.info("Sending Response");
		try {
			String serializedObj = xmlStream.toXML(input);
			exchange.sendResponseHeaders(code, serializedObj.length());
			OutputStream out = exchange.getResponseBody();
			out.write(serializedObj.getBytes());
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private User doValidation(HttpExchange exchange, String username, String password, Database db) {
		logger.info("Validating User Credentials");
		
		Headers reqHeaders = exchange.getRequestHeaders();
		List<String> list = reqHeaders.get("Host");
		SERVER_HOST_NAME = list.get(0);
		
		UsersDAO userDAO = db.getUsersDAO();
		User user = userDAO.readUser(username, password);

		return user;
	}

	private HttpHandler validateUser = new HttpHandler() {

		@Override
		public void handle(HttpExchange exchange) throws IOException {
			
			
			InputStream in = exchange.getRequestBody();

			ValidateUser_Input vuIn = (ValidateUser_Input) xmlStream
					.fromXML(in);

			Database db = new Database();
			db.startTransaction();
			User user = doValidation(exchange, vuIn.getUsername(), vuIn.getPassword(), db);
			db.endTransaction(true);

			String request = buildRequestString(exchange, xmlStream.toXML(vuIn));

			Headers headers = exchange.getResponseHeaders();
			headers.add("type", "ValidateUser_Output");

			if (user.getFirstName() == null) {
				ValidateUser_Output vuOut = new ValidateUser_Output(request);
				sendResponse(exchange, vuOut, 200);
				in.close();
				return;
			}

			ValidateUser_Output vuOut = new ValidateUser_Output(
					user.getFirstName(), user.getLastName(),
					user.getIndexedRecords(), request);

			in.close();

			sendResponse(exchange, vuOut, 200);
		}
	};

	private GetProjects_Output doParseProjects(Database db, String request) {
		try {

			GetProjects_Output gpOut = new GetProjects_Output(db.getDataDAO()
					.getAllProjects(), request);

			return gpOut;

		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}
	}

	private HttpHandler getProjects = new HttpHandler() {

		@Override
		public void handle(HttpExchange exchange) throws IOException {
			InputStream in = exchange.getRequestBody();
			ValidateUser_Input vuIn = (ValidateUser_Input) xmlStream
					.fromXML(in);
			String request = buildRequestString(exchange, xmlStream.toXML(vuIn));

			Database db = new Database();

			db.startTransaction();

			Headers headers = exchange.getResponseHeaders();
			headers.add("type", "GetProjects_Output");

			User user = doValidation(exchange, vuIn.getUsername(), vuIn.getPassword(), db);

			GetProjects_Output gpOut;

			if (user.getFirstName() == null) {
				gpOut = new GetProjects_Output(request);
				sendResponse(exchange, gpOut, 200);
				db.endTransaction(false);
				in.close();
				return;
			}

			gpOut = doParseProjects(db, request);

			sendResponse(exchange, gpOut, 200);
			in.close();

			db.endTransaction(true);

		}
	};

	private GetSampleImage_Output buildSampleImage(Database db, int projID,
			String request) {
		try {
			Image img = db.getDataDAO().readImage(1, projID);
			GetSampleImage_Output gsiOut;

			if (img.getFileURL() == null) {
				gsiOut = new GetSampleImage_Output(request);
				return gsiOut;
			}

			String url = "http://" + SERVER_HOST_NAME + "/" + img.getFileURL();
			gsiOut = new GetSampleImage_Output(url, request);

			return gsiOut;
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}

	}

	private HttpHandler getSampleImage = new HttpHandler() {

		@Override
		public void handle(HttpExchange exchange) throws IOException {
			InputStream in = exchange.getRequestBody();
			GetSampleImage_Input gsiIn = (GetSampleImage_Input) xmlStream
					.fromXML(in);
			String request = buildRequestString(exchange,
					xmlStream.toXML(gsiIn));
			Database db = new Database();
			db.startTransaction();

			User user = doValidation(exchange, gsiIn.getUsername(), gsiIn.getPassword(),
					db);

			Headers headers = exchange.getResponseHeaders();
			headers.add("type", "GetSampleImage_Output");
			
			GetSampleImage_Output gsiOut;

			if (user.getFirstName() == null) {
				gsiOut = new GetSampleImage_Output(request);
				sendResponse(exchange, gsiOut, 200);
				db.endTransaction(false);
				in.close();
				return;
			}

			gsiOut = buildSampleImage(db, gsiIn.getProjectID(), request);

			db.endTransaction(true);
			in.close();
			sendResponse(exchange, gsiOut, 200);
		}
	};

	private DownloadBatch_Output buildBatchOutput(Database db, DownloadBatch_Input dbIn, String request){
		
		return null;
	}
	
	private HttpHandler downloadBatch = new HttpHandler() {

		@Override
		public void handle(HttpExchange exchange) throws IOException {
			InputStream in = exchange.getRequestBody();
			DownloadBatch_Input dbIn = (DownloadBatch_Input) xmlStream.fromXML(in);
			String request = buildRequestString(exchange,
					xmlStream.toXML(dbIn));
			
			Database db = new Database();
			db.startTransaction();
			
			Headers headers = exchange.getResponseHeaders();
			headers.add("type", "DownloadBatch_Output");
			DownloadBatch_Output dbOut;
			User user = doValidation(exchange, dbIn.getUsername(), dbIn.getPassword(), db);
			
			if(user.getUsername() == null){
				dbOut = new DownloadBatch_Output(request);
				sendResponse(exchange, dbOut, 200);
				db.endTransaction(false);
				in.close();
				return;
			}
			
			dbOut = buildBatchOutput(db, dbIn, request);
			
			db.endTransaction(true);

		}
	};

	private HttpHandler submitBatch = new HttpHandler() {

		@Override
		public void handle(HttpExchange exchange) throws IOException {
			Database db = new Database();
			db.startTransaction();

			db.endTransaction(true);

		}
	};

	private HttpHandler getFields = new HttpHandler() {

		@Override
		public void handle(HttpExchange exchange) throws IOException {
			Database db = new Database();
			db.startTransaction();

			db.endTransaction(true);
		}
	};

	private HttpHandler search = new HttpHandler() {

		@Override
		public void handle(HttpExchange exchange) throws IOException {

			Database db = new Database();
			db.startTransaction();

			db.endTransaction(true);

		}
	};

	public static void main(String[] args) {
//		int port = Integer.parseInt(args[0]);
		new Server(8080).run();
//		ServerCommunicator comm = new ServerCommunicator(8080, "127.0.0.1");

	}
}
